今天來講一個時常被大家遺忘的 Widget:Flow。現在提到 flutter flow 大家第一個反應應該是 Flutter nocode 的工具,但其實 Flutter 中原本就存在一個名為 Flow 的 Widget,Flow Widget 是一個相對少用但功能強大的 Widget。它允許開發者直接控制其子元件的位置和大小,而無需透過框架的布局算法。可以達成一些不常見的客製化 UI。
FlowDelegate:這個 Delegate 包含繪製子元件的邏輯。主要方法有:
paintChildren
: 在此方法中,你會決定每個子元件的位置和大小。shouldRepaint
: 決定 Flow 是否需要重新繪製。shouldRelayout
: 決定 Flow 是否需要重children:要由 Flow 布局的子元件列表。
Flow Widget 依賴於兩個主要組件:
FlowDelegate
:這是用於決定子元件如何排列的邏輯。children
:這是一系列的子元件。FlowDelegate
的 paintChildren
方法允許你直接控制子元件的位置。
paintChildren
和 shouldRepaint
都會經常被呼叫,如果你的排列邏輯複雜,這可能會對性能產生影響。我們接下來要示範的如何透過 Flow 來達到 Wrap 的效果,在這個範例中,SpacingFlowDelegate
用來設置元件之間的間距,使每個元件之間都有一定的距離。
完整程式碼:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Flow(
delegate: SpacingFlowDelegate(spacing: 8.0),
children: List.generate(20, (index) {
return Container(
color: Colors.teal[100 * ((index % 5) + 1)],
width: 60.0,
height: 40.0,
);
}),
),
),
),
);
}
}
class SpacingFlowDelegate extends FlowDelegate {
final double spacing;
SpacingFlowDelegate({required this.spacing});
@override
void paintChildren(FlowPaintingContext context) {
double offsetX = 0.0;
double offsetY = 0.0;
int row = 0;
for (int i = 0; i < context.childCount; i++) {
final size = context.getChildSize(i);
if (offsetX + (size?.width ?? 0) > context.size.width) {
// Wrap to next line.
row++;
offsetX = 0.0;
offsetY = (size?.height ?? 0) * row;
}
context.paintChild(i,
transform:
Matrix4.translationValues(offsetX, offsetY + row * spacing, 0.0));
offsetX += (size?.width ?? 0) + spacing;
}
}
@override
bool shouldRepaint(SpacingFlowDelegate oldDelegate) {
return oldDelegate.spacing != spacing;
}
}
展示效果:
我們專注來看 paintChildren
,是如何幫助我實現這個目標
初始化偏移量:
double offsetX = 0.0;
double offsetY = 0.0;
初始化了兩個偏移量,offsetX
和 offsetY
,用於記錄當前子元件的要繪製的位置。
檢查是否需要換行:
if (offsetX + (size?.width ?? 0) > context.size.width) {
// Wrap to next line.
row++;
offsetX = 0.0;
offsetY = (size?.height ?? 0) * row;
}
檢查當前子元件的寬度加上其橫向偏移量是否超過了 Flow
的寬度。如果超過,則需要將元件放到下一行。知道要換行後,將 offsetX
重置為0,並根據當前的行數(row
)更新 offsetY
,使其下移。
繪製子元件:
context.paintChild(i,
transform:
Matrix4.translationValues(offsetX, offsetY + row * spacing, 0.0));
使用 paintChild
方法繪製子元件,並使用 Matrix4.translationValues
設置其位置。這裡,元件的 Y 軸偏移量不僅基於其在第幾行,還加上了額外的間距(row * spacing
)。
更新橫向偏移量:
offsetX += (size?.width ?? 0) + spacing;
繪製完當前的子元件後,更新 offsetX
,以便於下一個子元件可以放在其右側。這裡你也加上了一個 spacing
來確保子元件之間有足夠的間距。
透過以上的方法,成功地實現了一個自定義的 Wrap
效果。當子元件超出 Flow
Widget 的寬度時,它會自動移到下一行,且每行之間有一定的間距。
還可以加上動畫演示一下:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
)..addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(
_controller.isCompleted ? Icons.close : Icons.play_arrow,
),
onPressed: () {
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
),
body: SafeArea(
child: Flow(
delegate: SpacingFlowDelegate(spacing: 100.0 * _controller.value),
children: List.generate(20, (index) {
return Container(
color: Colors.teal[100 * ((index % 5) + 1)],
width: 60.0,
height: 40.0,
今天來講一個時常被大家遺忘的 Widget:Flow。現在提到 flutter flow 大家第一個反應應該是 Flutter nocode 的工具,但其實 Flutter 中原本就存在一個名為 Flow 的 Widget,Flow Widget 是一個相對少用但功能強大的 Widget。它允許開發者直接控制其子元件的位置和大小,而無需透過框架的布局算法。可以達成一些不常見的客製化 UI。 );
}),
),
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class SpacingFlowDelegate extends FlowDelegate {
final double spacing;
SpacingFlowDelegate({required this.spacing});
@override
void paintChildren(FlowPaintingContext context) {
double offsetX = 0.0;
double offsetY = 0.0;
int row = 0;
for (int i = 0; i < context.childCount; i++) {
final size = context.getChildSize(i);
if (offsetX + (size?.width ?? 0) > context.size.width) {
// Wrap to next line.
row++;
offsetX = 0.0;
offsetY = (size?.height ?? 0) * row;
}
context.paintChild(i,
transform:
Matrix4.translationValues(offsetX, offsetY + row * spacing, 0.0));
offsetX += (size?.width ?? 0) + spacing;
}
}
@override
bool shouldRepaint(SpacingFlowDelegate oldDelegate) {
return oldDelegate.spacing != spacing;
}
}
效果展示:
優點:
缺點:
Flow 是一個功能強大且具有高度靈活性的 Widget。雖然它可能不像其他常用的布局 Widgets 那麼知名,但在特定的使用情境下,它是一個不可或缺的工具。如同所有的工具,了解何時使用它和如何有效地使用它都是關鍵。希望這篇文章能夠幫助你了解和掌握 Flow Widget 的魔法,並鼓勵你在合適的場合嘗試使用它。